package PacMan;
import java.applet.Applet;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import Agent.Agent;
import Agent.PacmanFitnessFunction;
import dlife.ga.AveFitnessTracker;
import dlife.ga.CrossoverOperator;
import dlife.ga.DoubleGene;
import dlife.ga.FitnessFunction;
import dlife.ga.FixedRateMutation;
import dlife.ga.FixedSizePopulation;
import dlife.ga.Individual;
import dlife.ga.IndividualFitnessFunction;
import dlife.ga.MutationOperator;
import dlife.ga.OnePointCrossover;
import dlife.ga.ParentSelector;
import dlife.ga.Population;
import dlife.ga.PopulationStatTracker;
import dlife.ga.RouletteWheel;
import dlife.nn.FFNeuralNetwork;
import dlife.nn.SigmoidNeuron;

public class PacMan extends Applet implements IndividualFitnessFunction
{
  public GameModel      m_gameModel;
  static TopCanvas      m_topCanvas;
  static BottomCanvas   m_bottomCanvas;
  static GameUI         m_gameUI;
  static public Ticker         m_ticker;      // Used to update the game state and UI
  static SoundManager   m_soundMgr;
  static public int            m_globalTickCount = 0;
   
  static  int            m_ticksPerSec;    // These two variables control game speed 
  static int            m_delay;          // Milliseconds between ticks
   
   // [elijah.forbes.sumemrs] stuff
  static  public World world;
  static public Agent pacAI;
  static  int m_playTickCount = 0;
  static  int m_gameCount = 1;
   
  int currNum = 0;
  int currAvg = 0;
  int currRound = 0;

  public boolean[][] currExp = new boolean[28][31];	// tell whether we have visited that tile before
  public int newTileCount = 0; // how many new tiles do we go to
  
  
   // Evolution fields
   Individual exemplar;
   Individual bestEver;
   FitnessFunction fitnessFunction;
   MutationOperator mutation;
   CrossoverOperator cross;
   ParentSelector parentSelect;
   Population pop;
   PopulationStatTracker statTrack;
   int x;
   boolean done;
   
   // METHODS FOR EVOLUTION
   FileWriter fstream2 = null;
   BufferedWriter out2 = null;
   /**
    * Initialize the necessary evolution fields.
    */
   public void initializeEvolution()
   {	
	   System.out.println("++++++++++++++++++");
	   FileWriter fstream = null;
	   BufferedWriter out = null;
	  
	   exemplar = new Individual();
		for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
			exemplar.addGene(new DoubleGene());
		}
		
		
	   try {
		   fstream = new FileWriter( "..\\src\\best.txt");
			out = new BufferedWriter(fstream);
			fstream2 = new FileWriter( "..\\src\\avgByRun.txt");
			out2 = new BufferedWriter(fstream2);
			//out.write("hello world!");
			for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
				out.write(((DoubleGene)exemplar.getGene(i)).getValue()+",");
			}
			out.newLine();
	   } catch (IOException e1) {
		   System.out.println("-----------------------");
		   // TODO Auto-generated catch block
		   e1.printStackTrace();
	   }
		 
		mutation = new FixedRateMutation(SpeedTiming.mutationRate);
		cross = new OnePointCrossover();
		parentSelect = new RouletteWheel();
		fitnessFunction = new PacmanFitnessFunction();
		
		pop = new FixedSizePopulation(SpeedTiming.NUM_INDIVIDUALS, exemplar, parentSelect, mutation, cross, this, null);
		
		statTrack = new AveFitnessTracker(5);
		pop.addStatTracker(statTrack);
		
		pop.printStats();
		int x = 0;
		boolean done2 = false;
		Individual bestEver = pop.getBestIndividual();
		while(!done2) {
			pop.nextGeneration();
			if(pop.getBestIndividual().getFitness() > bestEver.getFitness()) {
				bestEver = pop.getBestIndividual();
			}
			if(x % 20 == 0) {
				Individual best = pop.getBestIndividual();
				for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
					System.out.print(((DoubleGene)best.getGene(i)).getValue()+",");
					try {
						out.write(((DoubleGene)best.getGene(i)).getValue()+",");
					} catch (IOException e) {	}
				}
				try {
					out.newLine();
				} catch (IOException e) {	}
				System.out.println();
			}
			x++;
			
			//This line sets the number of generations to run
			if(x > SpeedTiming.NUM_GENERATIONS) {
				done2 = true;
			}
		}
		
		//bestEver.write("./src/BestIndividual.txt");
		// write the best ever to a text file
		
		try {
			for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
				out.write(((DoubleGene)bestEver.getGene(i)).getValue()+",");
			}
			out.newLine();
			out.close();
			out2.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    
	    
		pop.printStats();
		
		System.out.println("Best Ever " +bestEver.getFitness() + " weights "); 
		for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
			System.out.print(((DoubleGene)bestEver.getGene(i)).getValue()+",");
		}
		System.out.println();
		
   }
   
   /**
    * Caclulates the fitness function
    */
   public double evaluate(Individual indi) {
		
	   if(this.currNum == SpeedTiming.NUM_INDIVIDUALS){
		   System.out.println(this.currRound + " avg :: " + (((double)this.currAvg) / ((double)(SpeedTiming.NUM_INDIVIDUALS))));
		   try {
			out2.write(this.currRound + ","+(((double)this.currAvg) / ((double)(SpeedTiming.NUM_INDIVIDUALS))));
			out2.newLine();
		   } catch (IOException e) {}
		   
		   this.currRound++;
		   this.currNum = 0;
		   this.currAvg = 0;
	   }
	   
	   this.currNum++;
	   
	   //currently have 2 layers 8 nodes in the first layer 4 in the second
		int[] layers = new int[]{24 + 9+4,4};
		double[] weights = new double[SpeedTiming.NUM_GENES];
		for(int i = 0; i < weights.length; i++) {
			weights[i] = ((DoubleGene)indi.getGene(i)).getValue();
		}
		FFNeuralNetwork network = new FFNeuralNetwork(layers, weights, new SigmoidNeuron());
		this.pacAI.setNetwork(network);
		
		//System.out.println("In evaluate" + this.x);
		/* run the pacman for number of lives */
		//Game should do this
		//network.setInputs(game.getInputs());
		this.m_gameModel.m_state = GameModel.STATE_NEWGAME;
		this.tick();
		
		/* for our exploration */
		currExp = new boolean[28][31];	// tell whether we have visited that tile before
		newTileCount = 0;
		
		int x=0,y=0,oldX=0, oldY=0;
		int count=0; int c1=0,c2=0,c3=0;
		while(this.m_gameModel.m_state != GameModel.STATE_NN ){ //myP.m_gameModel.STATE_DEADPACMAN){
			x = this.m_gameModel.m_player.m_locX;
			y = this.m_gameModel.m_player.m_locY;
			if(Math.abs(x-oldX) + Math.abs(y-oldY) >= 2){
				c1++;
				oldX =x; oldY = y;
			}
			if(!currExp[x][y]){
				currExp[x][y] = true;
				newTileCount++;
			}
			this.tick();
			count++;
			if(c1+1 % 1000 == 0)
				System.out.println("C1 = " + c1);
		}
		c1 += newTileCount * newTileCount;
		
		
		// now we need to reinitialize the game
		currExp = new boolean[28][31];	// tell whether we have visited that tile before
		newTileCount = 0;
		this.m_gameModel.m_state = GameModel.STATE_NEWGAME;
		this.tick();
		x=0;y=0;oldX=0; oldY=0;
		while(this.m_gameModel.m_state != GameModel.STATE_NN ){ //myP.m_gameModel.STATE_DEADPACMAN){
			this.tick();
			count++;
			x = this.m_gameModel.m_player.m_locX;
			y = this.m_gameModel.m_player.m_locY;
			if(Math.abs(x-oldX) + Math.abs(y-oldY) >= 2 ){
				c2++;
				oldX =x; oldY = y;
			}
			if(!currExp[x][y]){
				currExp[x][y] = true;
				newTileCount++;
			}
			if(c2+1 % 1000 == 0)
				System.out.println("C2 = " + c2);
		}
		c2 += newTileCount * newTileCount;
		
		// and for our third run
		currExp = new boolean[28][31];	// tell whether we have visited that tile before
		newTileCount = 0;
		this.m_gameModel.m_state = GameModel.STATE_NEWGAME;
		this.tick();
		while(this.m_gameModel.m_state != GameModel.STATE_NN ){ //myP.m_gameModel.STATE_DEADPACMAN){
			this.tick();
			count++;
			x = this.m_gameModel.m_player.m_locX;
			y = this.m_gameModel.m_player.m_locY;
			if(Math.abs(x-oldX) + Math.abs(y-oldY) >= 2){
				c3++;
				oldX =x; oldY = y;
			}
			if(!currExp[x][y]){
				currExp[x][y] = true;
				newTileCount++;
			}
			if(c3+1 % 1000 == 0)
				System.out.println("C3 = " + c3);
			
		}
		c3 += newTileCount * newTileCount;
		
		if(this.currRound == 0)
			count = 50;
		this.currAvg += ((double)c1+c2+c3)/3.0;
		if(true)
			return Math.min(Math.min(c1,c2), c3);
		
		if(c1>c2 && c2>c3 || c3>c2 && c2>c1)
			return c2;
		if(c2>c1 && c1>c3 || c3>c1 && c1>c2)
			return c1;
		if(true)
			return c3;
		
		//System.out.println("The count is " + count);
		return count;
		
		/*network.setInputs(new double[]{0,1,2,3,4,5,6,7});
		network.propagate();
		double[] outputs = network.getRawOutputs();
		
		double sum = 0;
		for(int i = 0; i < outputs.length; i++) {
			sum += outputs[i];
		}
		return sum;*/
		//return pacman.getTime();
	}
   
   /**
    * Updates to a new neural network
    */
   public void continueEvolution()
   {
	   this.pop.nextGeneration();
	   if (this.pop.getBestIndividual().getFitness() > bestEver.getFitness()) {
		   this.bestEver = pop.getBestIndividual();
	   }
	   if (this.x % 100 == 0) {
		   Individual best = pop.getBestIndividual();
		   for(int i = 0; i < SpeedTiming.NUM_GENES; i++) {
			   System.out.print(((DoubleGene)best.getGene(i)).getValue()+",");
		   }
		   System.out.println();
	   }
	   this.x++;
		
	   //This line sets the number of generations to run
	   if(this.x > SpeedTiming.NUM_GENERATIONS) {
		   this.done = true;
	   }
   }
   
   /**
    * Print results or do whatever wrap up is necessary
    */
   public void finishEvolution()
   {
		bestEver.write("BestIndividual.txt");
		pop.printStats();
		System.out.println("Best Ever " + bestEver.toString());
   }
   
   // END METHODS FOR EVOLUTION
	
   public void init ()
   {
      setTicksPerSec (SpeedTiming.numTicksPerSecond);
      // [elijah.forbes.sumemrs] Set-up logger
      try {
    	  Logger.init("PacLog.txt");
      } catch (IOException e) {
    	  // TODO Auto-generated catch block
    	  e.printStackTrace();
      }
      
     
      
      // Create canvases and layout
      m_gameModel = new GameModel (this); 
      m_gameUI = new GameUI (this, m_gameModel, 409, 450);
      m_topCanvas = new TopCanvas (m_gameModel, 200, 200);
      m_bottomCanvas = new BottomCanvas (this, m_gameModel, 200, 250);
       
      GridBagLayout gridBag = new GridBagLayout ();
      GridBagConstraints c = new GridBagConstraints ();
      
      setLayout (gridBag);
            
      c.gridwidth = 1;
      c.gridheight = 3;
      
      gridBag.setConstraints (m_gameUI, c);
      add (m_gameUI);
      
      c.gridwidth = GridBagConstraints.REMAINDER;
      c.gridheight = 1;
      gridBag.setConstraints (m_topCanvas, c);
      add (m_topCanvas);
      
      gridBag.setConstraints (m_bottomCanvas, c);
      add (m_bottomCanvas);
      
      requestFocus ();
      // Add event subscribers
      addKeyListener (new pacManKeyAdapter(this));
                
      validate ();
      m_soundMgr = new SoundManager (this, getCodeBase ());
      m_soundMgr.loadSoundClips ();
      
      System.out.println("Hello world !");
      /* call our evolution function */
      this.m_gameModel.m_state = GameModel.STATE_NEWGAME;
      this.tick();
      this.tick();
      this.initializeEvolution();
      System.out.println("Goodbye world");
      System.exit(0);
   }
   
   // Master ticker that runs various parts of the game
   // based on the GameModel's STATE
	public void tick ()
   {
		// [elijah.forbes.sumemrs] 
		if (pacAI == null && world != null) {
			pacAI = new Agent(world, 4, 3);
			pacAI.setPacman(this);
			System.out.println("Set it up");
		}
      //long temp = System.currentTimeMillis ();
      m_globalTickCount++;
      
      if (m_gameModel.m_state == GameModel.STATE_ABOUT)
      {
         m_soundMgr.stop ();
         m_gameModel.m_bIntroInited = false;
         m_gameUI.m_bShowIntro = false;
         m_gameUI.m_bShowAbout = true;
         m_gameUI.m_bRedrawAll = true;
         m_gameModel.m_nTicks2AboutShow++;
         if (m_gameModel.m_nTicks2AboutShow == 15000 / m_delay)
         {
            m_gameModel.m_state = GameModel.STATE_INTRO;
            m_gameModel.m_nTicks2AboutShow = 0;
         }
         
      } else if (m_gameModel.m_state == GameModel.STATE_INTRO)
      {
         tickIntro ();
         m_gameUI.m_bShowIntro = true;
         
      } else if (m_gameModel.m_state == GameModel.STATE_PAUSED)
      {
         m_gameUI.m_bDrawPaused = true;
         m_gameUI.m_bRedrawAll = true;
         m_gameUI.repaint ();
         return;
      
      } else if (m_gameModel.m_state == GameModel.STATE_NEWGAME)
      {
         m_soundMgr.stop ();
         m_gameModel.newGame ();
         m_gameModel.m_state = GameModel.STATE_BEGIN_PLAY;
         m_gameModel.m_nTicks2BeginPlay = 0;
         m_gameModel.m_bIntroInited = false;
         m_gameUI.m_bShowIntro = false;
         m_gameUI.m_bShowAbout = false;
         m_gameUI.m_bRedrawAll = true;
         
      } else if (m_gameModel.m_state == GameModel.STATE_GAMEOVER)
      {
         if (m_gameModel.m_nTicks2GameOver == 0)
         {
            if (m_gameModel.m_player.m_score > m_gameModel.m_highScore)
            {
               m_gameModel.m_highScore = m_gameModel.m_player.m_score;
               m_topCanvas.repaint ();  
            }   
         }
         
         m_gameModel.m_nTicks2GameOver++;
         
         // After 3 seconds go to the intro page
         if (m_gameModel.m_nTicks2GameOver == SpeedTiming.startGameTimer / m_delay)
         {
        	 if (!this.done)
        	 {
        		 continueEvolution();	 
        	 } else
        	 {
        		 finishEvolution();
        		 System.out.println("Done!");
        		 System.exit(0);
        	 }
        	 
            //m_gameModel.m_state = GameModel.STATE_INTRO;
        	 m_gameModel.m_state = GameModel.STATE_NEWGAME;
            m_gameModel.m_nTicks2GameOver = 0;
         }
         
         m_gameUI.m_bDrawGameOver = true;
         m_gameUI.m_bRedrawAll = true;
         m_gameUI.repaint ();
         return;
         
      } else if (m_gameModel.m_state == GameModel.STATE_LEVELCOMPLETE)
      {
         m_soundMgr.stop ();
         tickLevelComplete ();
      
      } else if (m_gameModel.m_state == GameModel.STATE_DEADPACMAN)
      {
    	 m_gameModel.m_state = GameModel.STATE_NN;
         /*m_soundMgr.stop ();
         if (m_gameModel.m_nLives == 0)
         {
            m_gameModel.m_state = GameModel.STATE_GAMEOVER;
            m_gameModel.m_nTicks2GameOver = 0;
            
         } else {
            m_gameModel.restartGame ();
            m_gameModel.m_state = GameModel.STATE_BEGIN_PLAY;
            m_bottomCanvas.repaint ();
         
         }*/
            
      } else if (m_gameModel.m_state == GameModel.STATE_BEGIN_PLAY)
      {
         tickBeginPlay ();
      
      } else if (m_gameModel.m_state == GameModel.STATE_PLAYING)
      {
         tickGamePlay ();
      } else if (m_gameModel.m_state == GameModel.STATE_DEAD_PLAY)
      {
         tickDeadPlay ();
      }
            
      m_gameUI.repaint();  
      m_topCanvas.repaint ();
     
	}
   
   // Ticked when level has completed
   public void tickLevelComplete ()
   {
      if (m_gameModel.m_nTicks2LevelComp == 0)
      {
         m_gameModel.setPausedGame (true);
         m_gameUI.m_bRedrawAll = true;
      }
      
      m_gameModel.m_nTicks2LevelComp++;
      
      // One second later, hide things and start flashing the board
      if (m_gameModel.m_nTicks2LevelComp == 600 / m_delay)
      {
         m_gameModel.setVisibleThings (false);
         m_gameModel.m_player.m_bVisible = true;
         m_gameUI.m_bFlipWallColor = true;
         m_gameUI.m_bRedrawAll = true;
         
      } else if (m_gameModel.m_nTicks2LevelComp > 600 / m_delay &&
                (m_gameModel.m_nTicks2LevelComp % (200 / m_delay)) == 0)
      {
         m_gameUI.m_bFlipWallColor = !m_gameUI.m_bFlipWallColor;
         m_gameUI.m_bRedrawAll = true;
      }
      
      if (m_gameModel.m_nTicks2LevelComp == 1900 / m_delay)
      {
         // This will advance the level and set the State to STATE_BEGIN_PLAY
         m_gameModel.loadNextLevel ();
         m_gameModel.m_state = GameModel.STATE_BEGIN_PLAY;
         m_gameModel.m_nTicks2LevelComp = 0;
         m_gameUI.m_bFlipWallColor = false;
         m_gameUI.m_bRedrawAll = true;
         m_bottomCanvas.repaint ();
      }
   }
   
   // Ticked when Pacman has died
   public void tickDeadPlay ()
   {
      /*if (m_gameModel.m_nTicks2DeadPlay == 0)
      {
         m_gameModel.setPausedGame (true);
         m_gameModel.m_player.m_rotationDying = 0;
         m_gameModel.m_player.m_mouthDegreeDying = 45;
         m_gameModel.m_player.m_mouthArcDying = 1;
         m_gameUI.m_bRedrawAll = true;
         m_gameModel.m_nOrigTicksPerSecond = m_ticksPerSec;
         setTicksPerSec (SpeedTiming.deadTickRate);
         m_soundMgr.stop ();
      }
      
      m_gameModel.m_nTicks2DeadPlay++;
      
      if (m_gameModel.m_nTicks2DeadPlay == SpeedTiming.numTicks2DeadPlay / m_delay)
      {
         m_gameModel.m_player.m_bDrawDead = true;
         for (int i = 0; i < m_gameModel.m_ghosts.length; i++)
         {
            m_gameModel.m_ghosts[i].setVisible (false); 
         }
         m_gameModel.m_fruit.setVisible (false);
         m_gameUI.m_bRedrawAll = true;
         m_soundMgr.playSound (SoundManager.SOUND_PACMANDIES);
      }
      
      if (m_gameModel.m_nTicks2DeadPlay == (SoundManager.SOUND_PACMANDIES_LENGTH + 1000) / m_delay)
      {
         m_gameModel.m_state = GameModel.STATE_DEADPACMAN;  //STATE_LEVELCOMPLETE
         m_gameModel.m_nTicks2DeadPlay = 0;
         setTicksPerSec (m_gameModel.m_nOrigTicksPerSecond);
         m_gameUI.m_bRedrawAll = true;
      }*/
	   m_gameModel.m_state = GameModel.STATE_DEADPACMAN;
	   m_gameUI.m_bRedrawAll = true;
   }
      
   // Ticked when the game is about to begin play
   public void tickBeginPlay ()
   {
	   /*
	    * Set up our pacAI
	    */
	   pacAI.setGameModel(this.m_gameModel);
	  m_playTickCount = 0;
	   
      if (m_gameModel.m_nTicks2BeginPlay == 0)
      {
         m_gameModel.setVisibleThings (false);
         m_gameModel.setPausedGame (true);
         m_gameUI.m_bDrawReady = true;
         m_gameUI.m_bDrawGameOver = false;
         m_gameUI.m_bRedrawAll = true;
         m_gameUI.m_bFlipWallColor = false;
         m_gameUI.refreshRedrawHash ();
         if (m_gameModel.m_bPlayStartClip)
         {
            m_soundMgr.playSound (SoundManager.SOUND_START);
            m_gameModel.m_bPlayStartClip = false;
         } 
         m_bottomCanvas.repaint (); 
      }
      
      m_gameModel.m_nTicks2BeginPlay++;
      
      if (true)//m_gameModel.m_nTicks2BeginPlay == 500 / m_delay)
      {
         m_gameModel.setVisibleThings (true);
         m_gameModel.m_fruit.setVisible (false);
      }
      
      if (true)//(m_gameModel.m_nTicks2BeginPlay == SoundManager.SOUND_START_LENGTH / m_delay && !m_gameModel.m_bStartClipPlayed) ||  (m_gameModel.m_nTicks2BeginPlay == 1000 / m_delay && m_gameModel.m_bStartClipPlayed))
      {
         m_gameModel.m_state = GameModel.STATE_PLAYING;
         m_gameModel.setVisibleThings (true);
         m_gameModel.m_fruit.setVisible (false);
         m_gameModel.setPausedGame (false);
         m_gameUI.m_bDrawReady = false;
         m_gameUI.m_bRedrawAll = true;
         m_gameModel.m_nTicks2BeginPlay = 0;
         m_gameModel.m_bStartClipPlayed = true;
         m_soundMgr.playSound (SoundManager.SOUND_SIREN);
      }
   }
   
   // Ticked when the game is playing normally
   public void tickGamePlay ()
   {
	   // [elijah.forbes.sumemrs] 
	   m_playTickCount++;
      boolean  bFleeing = false;
      int      nCollisionCode;
      
      // Check if player has earned free life
      if (m_gameModel.m_player.m_score >= m_gameModel.m_nextFreeUp)
      {
       //  m_soundMgr.playSound (SoundManager.SOUND_EXTRAPAC);
         m_gameModel.m_nLives += 1;
         m_gameModel.m_nextFreeUp += 10000;
         m_bottomCanvas.repaint ();
      }
      
      
      int x = this.m_gameModel.m_player.m_locX;
      int y = this.m_gameModel.m_player.m_locY;
      Ghost[]  ghosts = this.m_gameModel.m_ghosts;  
      int[][] posG = new int[4][2];
		posG[0][0] = ghosts[0].m_locX;
		posG[0][1] = ghosts[0].m_locY;
		posG[1][0] = ghosts[1].m_locX;
		posG[1][1] = ghosts[1].m_locY;
		posG[2][0] = ghosts[2].m_locX;
		posG[2][1] = ghosts[2].m_locY;
		posG[3][0] = ghosts[3].m_locX;
		posG[3][1] = ghosts[3].m_locY;
	  for(int u=0; u<4; u++){
		  if(x == posG[u][0] && y == posG[u][1]){
		//	  System.err.println("Collision with count at " + m_playTickCount);
			  m_gameModel.m_state = GameModel.STATE_NN; //STATE_LEVELCOMPLETE;
	            m_gameModel.m_player.m_direction = Thing.STILL;
	            m_gameModel.m_nTicks2DeadPlay = 0;
	            m_gameCount++;
	            world.dead = true;
	            Logger.log(Integer.toString(m_playTickCount));
	            m_playTickCount = 0;
	            return;
		  }
	  }
       
      
      
      // Check for collisions between Things and Pacman
      for (int i =0; i < m_gameModel.m_things.length; i++)
      {
         nCollisionCode = m_gameModel.m_things[i].checkCollision (m_gameModel.m_player);
         
         
         
         if (nCollisionCode == 1) // Ghost was eaten
         {
        	 System.err.println("Cant eat ghosts");
            m_soundMgr.playSound (SoundManager.SOUND_EATGHOST);
            break; // Must be eaten one tick at a time
         } else if (nCollisionCode == 2) // Pacman was caught.
         {
        //	 System.out.println("Pacman caught " + m_playTickCount);
        	/* Ghost[]  ghosts = this.m_gameModel.m_ghosts;  
     		int[][] posG = new int[4][2];
     		posG[0][0] = ghosts[0].m_locX;
     		posG[0][1] = ghosts[0].m_locY;
     		posG[1][0] = ghosts[1].m_locX;
     		posG[1][1] = ghosts[1].m_locY;
     		posG[2][0] = ghosts[2].m_locX;
     		posG[2][1] = ghosts[2].m_locY;
     		posG[3][0] = ghosts[3].m_locX;
     		posG[3][1] = ghosts[3].m_locY;
     		for(int u=0; u<4; u++)
     			System.out.println("Ghost at " + posG[u][0] + "," + posG[u][1]);
     		System.out.println("player at " + this.m_gameModel.m_player.m_locX + "," + this.m_gameModel.m_player.m_locY );*/
     		
     		
            m_gameModel.m_state = GameModel.STATE_NN; //STATE_LEVELCOMPLETE;
            m_gameModel.m_player.m_direction = Thing.STILL;
            m_gameModel.m_nTicks2DeadPlay = 0;
            m_gameCount++;
            world.dead = true;
            Logger.log(Integer.toString(m_playTickCount));
            m_playTickCount = 0;
            return;
         } else if (nCollisionCode == 3) // Pacman ate a Fruit
         {
            m_soundMgr.playSound (SoundManager.SOUND_EATFRUIT);
            break; // Must be eaten one tick at a time // TODO
         }
      }
      
      // [elijah.forbes.sumemrs] update ghost locations
      for (int i = 0; i < m_gameModel.m_ghosts.length; i++) {
    	  world.ghostLocations[i][0] = m_gameModel.m_ghosts[i].m_locX;
    	  world.ghostLocations[i][1] = m_gameModel.m_ghosts[i].m_locY;
      }
      // [elijah.forbes.sumemrs] Let agent know what's up and get their move.
      try{
    	  m_gameModel.m_player.m_requestedDirection = pacAI.tick();
      }catch(Exception e){
    	  System.out.println("X = " + m_gameModel.m_player.m_locX);
    	  System.out.println("Y = " + m_gameModel.m_player.m_locY);
    	  System.out.println(this.world.tiles.length);
    	  System.out.println(this.world.tiles[0].length);
    	 e.printStackTrace();
    	  System.exit(1);
      }
      
      // Tick and then Move each Thing (includes Pacman and Ghosts)
      for (int i = 0; i < m_gameModel.m_things.length; i++)
      {
         m_gameModel.m_things[i].tickThing ();     
         if (m_gameModel.m_things[i].canMove ())
            Move (m_gameModel.m_things[i]);    
      }
      
      // Check to see if there are any fleeing Ghosts left
      // because of a power up pacman ate.
      for (int i =0; i < m_gameModel.m_ghosts.length; i++)
      {
         bFleeing |= m_gameModel.m_ghosts[i].m_nTicks2Flee > 0;
      }
      // If no fleeing ghosts, then reset the Power Up eat ghost score back to 200
      // and kill the BlueGhost loop
      if (bFleeing != true)
      {
         m_gameModel.m_eatGhostPoints = 200;
        // m_soundMgr.stopSound (SoundManager.SOUND_GHOSTBLUE);
        // m_soundMgr.playSound (SoundManager.SOUND_SIREN);
      }
         
      if (m_gameModel.m_totalFoodCount == m_gameModel.m_currentFoodCount)
      {
         m_gameModel.m_state = GameModel.STATE_LEVELCOMPLETE;
         m_gameModel.m_nTicks2LevelComp = 0;
      }
      // Tick the sound manager (mainly to check if the Chomping loop needs to be stopped)
     // m_soundMgr.tickSound ();
   }
   
   // Ticked when the game is running the intro
   public void tickIntro ()
   {
      boolean  bFleeing = false;
      int      nCollisionCode;
      
      if (!m_gameModel.m_bIntroInited)
      {
         m_gameModel.initIntro ();
         setTicksPerSec (SpeedTiming.numTicksInIntro);
         m_gameModel.m_bIntroInited = true;
         m_gameUI.m_bRedrawAll = true;
      }
      
      // Check if Ghost has run to the left of the Runway
      for (int i =0; i < m_gameModel.m_ghosts.length; i++)
      {
         if (m_gameModel.m_ghosts[i].m_locX == 19)
            m_gameModel.m_ghosts[i].m_bPaused = true;
      }
      
      if (!m_gameModel.m_ghosts[0].m_bVisible)
      {
         m_gameModel.m_ghosts[0].m_bVisible = true;
         m_gameModel.m_ghosts[0].m_bPaused = false;
      }
      
      if (!m_gameModel.m_ghosts[1].m_bVisible && m_gameModel.m_ghosts[0].m_locX == 19)
      {
         m_gameModel.m_ghosts[1].m_bVisible = true;
         m_gameModel.m_ghosts[1].m_bPaused = false;
      }
      
      if (!m_gameModel.m_ghosts[2].m_bVisible && m_gameModel.m_ghosts[1].m_locX == 19)
      {
         m_gameModel.m_ghosts[2].m_bVisible = true;
         m_gameModel.m_ghosts[2].m_bPaused = false;
      }
      
      if (!m_gameModel.m_ghosts[3].m_bVisible && m_gameModel.m_ghosts[2].m_locX == 19)
      {
         m_gameModel.m_ghosts[3].m_bVisible = true;
         m_gameModel.m_ghosts[3].m_bPaused = false;
      }
      
      if (!m_gameModel.m_player.m_bVisible && m_gameModel.m_ghosts[3].m_locX == 19)
      {
         m_gameModel.m_player.m_bVisible = true;
         m_gameModel.m_player.m_bPaused = false;
      }
      
      if (m_gameModel.m_player.m_locX == 23)
         m_gameModel.m_player.m_requestedDirection = Thing.LEFT;
      
      if (m_gameModel.m_player.m_locX == 5)
         m_gameModel.m_player.m_requestedDirection = Thing.RIGHT;
      
      // Tick and then Move each Thing (includes Pacman and Ghosts)
      for (int i = 0; i < m_gameModel.m_things.length; i++)
      {
         m_gameModel.m_things[i].tickThing ();     
         if (m_gameModel.m_things[i].canMove ())
            Move (m_gameModel.m_things[i]);    
      }
   }
   
   // This method is called to update the Thing's Location and delta Locations
   // based on Thing's m_direction.  The ONLY update to Thing's m_direction is
   // if Thing hits a wall and m_direction is set to STILL.  Otherwise, all 
   // m_direction changes occur in the Thing's virtual method tickThing ().
	public void Move (Thing thing)
   {
      if (thing.m_direction == Thing.STILL)
         return;
      
      boolean bMoved = false;
      
      thing.m_lastLocX = thing.m_locX;
      thing.m_lastLocY = thing.m_locY;
      thing.m_lastDeltaLocX = thing.m_deltaLocX;
      thing.m_lastDeltaLocY = thing.m_deltaLocY;
         
      // See if thing can eat any nearby items
      // [elijah.forbes.sumemrs] We don't eat.
//      thing.eatItem (GameModel.GS_FOOD);
//      thing.eatItem (GameModel.GS_POWERUP);
               
      // Based on the current direction, update thing's location in that direction.
      // The thing.m_deltaLocX != 0 is so that if the thing is in a cell with a wall
      // and the thing is directed towards the wall, he will still need to move towards the
      // wall until the thing is dead center in the cell.
      if (thing.m_direction == Thing.LEFT &&
          ((m_gameModel.m_gameState [thing.m_locX][thing.m_locY] & GameModel.GS_WEST) == 0 || thing.m_deltaLocX != 0))
      {
         thing.m_deltaLocX--;
         bMoved = true;
      } else if (thing.m_direction == Thing.RIGHT &&
          ((m_gameModel.m_gameState [thing.m_locX][thing.m_locY] & GameModel.GS_EAST) == 0 || thing.m_deltaLocX != 0))
      {
         thing.m_deltaLocX++;
         bMoved = true;
      } else if (thing.m_direction == Thing.UP &&
          ((m_gameModel.m_gameState [thing.m_locX][thing.m_locY] & GameModel.GS_NORTH) == 0 || thing.m_deltaLocY != 0))
      {
         thing.m_deltaLocY--;
         bMoved = true;
      } else if (thing.m_direction == Thing.DOWN &&
          ((m_gameModel.m_gameState [thing.m_locX][thing.m_locY] & GameModel.GS_SOUTH) == 0 || thing.m_deltaLocY != 0))
      {
         thing.m_deltaLocY++; 
         bMoved = true;
      }
         
      // If the thing has moved past the middle of the two cells, then switch his
      // location to the other side.
      if (thing.m_deltaLocX <= -thing.m_deltaMax) // Shift thing to adjacent cell on left
      {
         if (thing.m_locX != 0)
         {
            thing.m_deltaLocX = thing.m_deltaMax - 1;
            thing.m_locX--;
            bMoved = true;
            
         } else {
            // Check to see if thing should warp to right side
            if (thing.m_deltaLocX < -thing.m_deltaMax)
            {
               thing.m_deltaLocX = thing.m_deltaMax - 1;
               thing.m_locX = m_gameModel.m_gameSizeX - 1;
               bMoved = true;
            }
         }
      } else if (thing.m_deltaLocX >= thing.m_deltaMax)  // Shift thing to adjacent cell on right
      {
         if (thing.m_locX != m_gameModel.m_gameSizeX - 1)
         {
            thing.m_deltaLocX = 1 - thing.m_deltaMax;
            thing.m_locX++;
            bMoved = true;
         } else {
            // Check to see if thing should warp to left side
            if (thing.m_deltaLocX > thing.m_deltaMax)
            {
               thing.m_deltaLocX = 1 - thing.m_deltaMax;
               thing.m_locX = 0;
               bMoved = true;
            }
         }
      } else if (thing.m_deltaLocY <= -thing.m_deltaMax) // Shift thing to adjacent cell on top
      {
        if (thing.m_locY != 0)
         {
            thing.m_deltaLocY = thing.m_deltaMax - 1;
            thing.m_locY--;
            bMoved = true;
            
         } else {
            // Check to see if thing should warp to bottom side
            if (thing.m_deltaLocY < -thing.m_deltaMax)
            {
               thing.m_deltaLocY = thing.m_deltaMax - 1;
               thing.m_locY = m_gameModel.m_gameSizeY - 1;
               bMoved = true;
            }
         }
      } else if (thing.m_deltaLocY >= thing.m_deltaMax) // Shift thing to adjacent cell on bottom
      {
         if (thing.m_locY != m_gameModel.m_gameSizeY - 1)
         {
            thing.m_deltaLocY = 1 - thing.m_deltaMax;
            thing.m_locY++;
            bMoved = true;
         } else {
            // Check to see if thing should warp to top side
            if (thing.m_deltaLocY > thing.m_deltaMax)
            {
               thing.m_deltaLocY = 1 - thing.m_deltaMax;
               thing.m_locY = 0;
               bMoved = true;
            }
         }                  
      }
      
      if (!bMoved)
         thing.m_direction = Thing.STILL;
	}
   
   public void start ()
   {
      if (m_ticker == null)
      {
         m_ticker = new Ticker (this);
         //m_ticker.start ();
      }
   }
   
   public void stop ()
   {
      m_ticker = null;
      m_soundMgr.stop ();
   }
   
   void setTicksPerSec (int ticks)
   {
      m_ticksPerSec = ticks;
      m_delay = 1000 / m_ticksPerSec;
   }   
   
   void toggleGhostAI ()
   {
      for (int i = 0; i < m_gameModel.m_ghosts.length; i++)
      {
         m_gameModel.m_ghosts[i].m_bInsaneAI = !m_gameModel.m_ghosts[i].m_bInsaneAI;
      }
   }
   
   /* Can't run Pacman as an application since it use sound-related methods.
   public static void main (String args[])
   {
      // Create new window
      MainFrame frame = new MainFrame ("PacMan");
      
      // Create PacMan instance
      PacMan pacMan = new PacMan ();
      
      // Initialize instance
      pacMan.init ();
      
      frame.add ("Center", pacMan);
      frame.pack ();
      frame.show ();
      
      pacMan.start ();
   }
   */
}

/*
class MainFrame extends Frame
{
   MainFrame (String title)
   {
      super (title);
   }
   
   public boolean handleEvent (Event e)
   {
      if (e.id ==Event.WINDOW_DESTROY)
      {
         System.exit (0);
      }
      return super.handleEvent (e);
   }
}
*/

// Ticker thread that updates the game state and refreshes the UI
// 
class Ticker //extends Thread
{

	PacMan      m_pacMan;
   
	public Ticker(PacMan pacMan)
   {
		m_pacMan = pacMan;
   }

	public void run()
   {
    //  while (Thread.currentThread () == m_pacMan.m_ticker)
    //  {
    	  // [elijah.forbes.sumemrs] Lets go really fast...
//         try {
//            this.sleep (m_pacMan.m_delay);
//            
//         } catch (InterruptedException e) {
//            break;
//         }
         
         m_pacMan.tick ();
         m_pacMan.requestFocus ();
   //   }
	}
}

// Key event handlers
class pacManKeyAdapter extends KeyAdapter
{
   PacMan   m_pacMan;
   
   pacManKeyAdapter (PacMan pacMan)
   {
      super ();
      m_pacMan = pacMan;
   }
   
   public void keyPressed(KeyEvent event)
   {
      switch (event.getKeyCode()) 
      {
         
      // [elijah.forbes.sumemrs] We don't want control over The PacMan
//      case KeyEvent.VK_LEFT:
//      	   m_pacMan.m_gameModel.m_player.m_requestedDirection = Thing.LEFT;
//            break;
//      
//      case KeyEvent.VK_RIGHT:
//			   m_pacMan.m_gameModel.m_player.m_requestedDirection = Thing.RIGHT;
//            break;
//      
//      case KeyEvent.VK_UP:
//            m_pacMan.m_gameModel.m_player.m_requestedDirection = Thing.UP;
//			   break;
//      
//      case KeyEvent.VK_DOWN:
//				m_pacMan.m_gameModel.m_player.m_requestedDirection = Thing.DOWN;
//            break;
      
      case KeyEvent.VK_N:
            m_pacMan.m_gameModel.m_state = GameModel.STATE_NEWGAME;
            m_pacMan.m_gameUI.m_bDrawPaused = false;
            break;
      
      case KeyEvent.VK_P:
            if (m_pacMan.m_gameModel.m_state == GameModel.STATE_GAMEOVER)
               break;
            
            if (m_pacMan.m_gameModel.m_state == GameModel.STATE_PAUSED)
            {
               m_pacMan.m_gameModel.m_state = m_pacMan.m_gameModel.m_pausedState;
               m_pacMan.m_gameUI.m_bDrawPaused = false;
               m_pacMan.m_gameUI.m_bRedrawAll = true;
               
            } else {
               m_pacMan.m_gameModel.m_pausedState = m_pacMan.m_gameModel.m_state;
               m_pacMan.m_gameModel.m_state = GameModel.STATE_PAUSED;
            }
            break;
            
      case KeyEvent.VK_A:
            m_pacMan.m_gameModel.m_state = GameModel.STATE_ABOUT;
            m_pacMan.m_gameModel.m_nTicks2AboutShow = 0;
            break;
            
      case KeyEvent.VK_S:
            m_pacMan.m_soundMgr.m_bEnabled = !m_pacMan.m_soundMgr.m_bEnabled;
            if (m_pacMan.m_soundMgr.m_bEnabled == false)
               m_pacMan.m_soundMgr.stop ();
            m_pacMan.m_bottomCanvas.repaint ();
            break;
             
      case KeyEvent.VK_I:
            m_pacMan.toggleGhostAI ();
            break;
      
      default:
				System.out.println("Hello World!");
      }   
   }
}


